package com.cn.mvcfw.servlet;

import com.cn.mvcfw.annotation.Autowired;
import com.cn.mvcfw.annotation.Controller;
import com.cn.mvcfw.annotation.RequestMapping;
import com.cn.mvcfw.annotation.Service;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * Created by
 *
 * @author touni
 * @date 2019/3/27
 * @time 11:05
 */
public class TDispatcherServlet extends HttpServlet {

    private Properties properties = new Properties();
    private List<String> classFilesName = new ArrayList<>();
    private Map<String,Object> iocMap = new HashMap<>();
    private Map<String,Method> handlerMapping = new HashMap<>();


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispartcher(req,resp);
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }

    private void doDispartcher(HttpServletRequest request, HttpServletResponse response) throws IOException, InvocationTargetException, IllegalAccessException {
        if (handlerMapping.isEmpty()) {return;}
        String requestURI = request.getRequestURI();
        String contextPath = request.getContextPath();
        //去掉requestURI的前缀和多余的/
        String url = requestURI.replace(contextPath,"").replaceAll("/+","/").trim();
        if (!handlerMapping.containsKey(url)) {
            response.getWriter().println("404,    NOt FOUND");
        }
        Method method = handlerMapping.get(url);
        //执行方法  获取方法的参数  和执行对象    todo  0.0版本 直接固定参数不考虑多个参数
        String nameValue = request.getParameter("name");
        String beanName = toLowFirstClassName(method.getDeclaringClass().getSimpleName());


        method.invoke(iocMap.get(beanName),request,response,nameValue);



    }


    @Override
    public void init(ServletConfig config) throws ServletException {
        //1.加载配置文件  在web.xml中扫描对应的key 获取其值
        loadConfigFile(config.getInitParameter("contextConfigLocation"));
        //2.扫描配置文件  将所有的class文件封装到集合中
        scanConfigFile((String) properties.get("context-scan-package"));
        //3.进行初始化  实例化带注解的类  反射获取对象 封装IOC集合
        doIocMap();
        //4.完成依赖注入  将IOC容器中没有赋值的属性进行赋值  在此主要处理Autowired注解
        doAutowired();
        //5.封装handlermapping  将URL和method一一对应
        dohandlerMapping();

        System.out.println("spring初始化完成！！！");







    }

    /**
     * 将URL和method一一对应
     */
    private void dohandlerMapping() {

        if (iocMap.isEmpty()){return;}
        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            //取出controller注解的类
            Class<?> clazz = entry.getValue().getClass();
            String url = "/";   //定义URL
            if (clazz.isAnnotationPresent(Controller.class)){
                //判断并取出类上的requestMapping注解
                if (clazz.isAnnotationPresent(RequestMapping.class)){
                    RequestMapping classRequestMapping = clazz.getAnnotation(RequestMapping.class);
                    url = url + "/" + classRequestMapping.value();

                }
                //取出所有的方法和方法上的requestmapping   遵循spring原则，不获取private方法
                Method[] methods = clazz.getMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(RequestMapping.class)){
                        RequestMapping methodRequestMapping = method.getAnnotation(RequestMapping.class);
                        String urlreal = url + "/" + methodRequestMapping.value();
                        handlerMapping.put(urlreal.replaceAll("/+","/"),method);
                    }
                }






            }


        }






    }


    /**
     * 完成带有Autowired注解的封装
     */
    private void doAutowired() {
        if (iocMap.isEmpty()){ return;}
        for (Map.Entry<String, Object> entry : iocMap.entrySet()) {
            //获取所有字段的值 包括private等 但是不包括父类
            Field[] fields = entry.getValue().getClass().getDeclaredFields();

            for (Field field : fields) {
                //暂时只考虑Autowired注解
                if (field.isAnnotationPresent(Autowired.class)){
                    try {
                        Autowired annotation = field.getAnnotation(Autowired.class);
                        System.out.println(annotation.value());


                        //暴力赋值 避免private属性无法赋值
                        field.setAccessible(true);
                        //根据接口名称赋值
                        field.set(entry.getValue(),iocMap.get(field.getType().getName()));


                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }

                }
                continue;


            }


        }






    }

    /**
     * 将带注解的类实例化，并放入到IOC容器中   仅仅写了单例模式下的
     */
    private void doIocMap() {
        //此处的className是全限定名
        for (String className :classFilesName){
            //反射获取对象
            try {
                Class<?> classz = Class.forName(className);
                //重新封装类名
                className = className.substring(className.lastIndexOf(".")+1);
                //反射获取带有controller注解的类
                if (classz.isAnnotationPresent(Controller.class)){
                    //将带有Controller注解的类放入IOC容器中  spring中默认可以的名称是类名，其中类名首字母必须小写
                    iocMap.put(toLowFirstClassName(className),classz.newInstance());
                }else if (classz.isAnnotationPresent(Service.class)){   // TODO  未搞懂三种命名方式的优先级
                    //key的取值可能有三种情况 1.默认的类名首字母小写  2.接口名字   3.注解里面定义的名称
                    //1.默认的类名首字母小写  或者注解里面定义的名称
                    //取出注解的值
                    Service annotation = classz.getAnnotation(Service.class);
                    String annotationValue = annotation.value();
                    //如果注解的值为空  则是默认类名的首字母小写
                    if ("".equals(annotationValue)){
                        iocMap.put(toLowFirstClassName(className),classz.newInstance());
                    }else {
                        // 如果注解值不为空 先判断是否有相同名称的key 有则报错 则使用注解里面定义的名称
                        if (iocMap.containsKey(annotationValue)) {
                            throw new RuntimeException("出现严重错误，bean的名称命名重复！！！");
                        }
                        iocMap.put(annotationValue, classz.newInstance());
                    }
                    //2.接口名字   接口名字是带包名的 不需要进行首字母小写
                    Class<?>[] interfaces = classz.getInterfaces();
                    for (Class<?> ainterface :interfaces){
                        if (iocMap.containsKey(ainterface.getName())){
                            throw new RuntimeException("出现严重错误  一个接口有多个实现类！！！ 请添加相应的注解value的值");
                        }
                        //接口默认带的是全限定名
                        iocMap.put(ainterface.getName(),classz.newInstance());
                    }
                }else {
                    //若不带controller service 不进行实例化
                    continue;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 将第接口名或者类名的一个字母小写
     * @param className
     * @return
     */
    public String toLowFirstClassName(String className){
        char[] chars = className.toCharArray();
        chars[0] +=32;
        return String.valueOf(chars);


    }






    /**
     * 扫描class文件，放入到集合中
     * @param scanPackage
     */
    private void scanConfigFile(String scanPackage) {
        URL url = this.getClass().getClassLoader().getResource(scanPackage.replace("\\.","/"));  //获取的是这个包的绝对路径名称 里面必须为/不能为. 如果需要获取下面的子包，需要加上父包名称
        File files = new File(url.getFile());
        for (File file : files.listFiles()){
            //获取文件或者文件夹的名称
            String fileName = file.getName();
            if (file.isDirectory()){
                scanConfigFile(scanPackage+"/"+fileName);
            }
            if (fileName.endsWith(".class")){
                //必须存入包全民 带.
                classFilesName.add(scanPackage.replace("/",".")+"."+fileName.replace(".class","").trim());
            }
        }
    }

    /**
     * 加载配置文件
     * @param contextConfigLocation
     */
    private void loadConfigFile(String contextConfigLocation) {
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);

        try {
            properties.load(is);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }


    }


}
